/*******************************************************************\
| Color.h
|
| CREATED:		23.5.2009
| DESCRIPTION:
| 
| Helper class for color calculations
\********************************************************************/
#ifndef COLOR_H
#define COLOR_H

#include "Math/Maths.h"

namespace PhotonMapping
{


class Color
{
	public:		
		// the color's components
		union
		{
			struct
			{
				float m_R;
				float m_G;
				float m_B;
			};
			float m_RGB[3];
		};

	public:
		// constructors
		inline Color();
		inline Color(const Color& p_Col);
		inline Color(const float p_R, const float p_G, const float p_B);

	public:
		// + and += operators
		inline Color& operator+=(const Color& p_Col);
		inline Color& operator*=(const Color& p_Col);
		inline Color& operator*=(const float p_Factor);
		inline friend const Color operator+(const Color& p_Col1, const Color& p_Col2);
		inline friend const Color operator-(const Color& p_Col1, const Color& p_Col2);
		inline friend const Color operator*(const Color& p_Col1, const Color& p_Col2);
		inline friend const Color operator*(const Color& p_Col, const float p_Factor);
		inline friend const Color operator*(const float p_Factor, const Color& p_Col);

		inline friend const Color operator/(const Color& p_Col1, const Color& p_Col2);
		inline friend const Color operator/(const Color& p_Col, const float p_Factor);
		inline friend const Color operator/(const float p_Factor, const Color& p_Col);

		// sum and average of all 3 components
		float Sum() const;
		float Average() const;
		bool IsZero() const;

		// RGBE conversion functions
		inline void ToRGBE(byte rgbe[4]) const;
		inline void FromRGBE(const byte rgbe[4]);

	public:
		// special predefined values
		static const Color ZERO;
		static const Color ONE;
};


/* ***************************************************************** *\
|  INLINE FUNCTIONS
\* ***************************************************************** */
inline Color::Color() 
{ 
}

inline Color::Color(const float p_R, const float p_G, const float p_B)
{ 
	m_R = p_R;
	m_G = p_G;
	m_B = p_B;
}

inline Color::Color(const Color& p_Col)
{
	m_R = p_Col.m_R;
	m_G = p_Col.m_G;
	m_B = p_Col.m_B;
}

// += operator
inline Color& Color::operator+=(const Color& p_Col)
{
	m_R += p_Col.m_R;
	m_G += p_Col.m_G;
	m_B += p_Col.m_B;

	return (*this);
}

// *= operator
inline Color& Color::operator*=(const Color& p_Col)
{
	m_R *= p_Col.m_R;
	m_G *= p_Col.m_G;
	m_B *= p_Col.m_B;

	return (*this);
}

// *= operator
inline Color& Color::operator*=(const float p_Factor)
{
	m_R *= p_Factor;
	m_G *= p_Factor;
	m_B *= p_Factor;

	return (*this);
}

// friend function for addition and multiplication
inline const Color operator+(const Color& p_Col1, const Color& p_Col2)
{
	return Color(p_Col1.m_R + p_Col2.m_R, p_Col1.m_G + p_Col2.m_G, p_Col1.m_B + p_Col2.m_B);
}

inline const Color operator-(const Color& p_Col1, const Color& p_Col2)
{
	return Color(p_Col1.m_R - p_Col2.m_R, p_Col1.m_G - p_Col2.m_G, p_Col1.m_B - p_Col2.m_B);
}

inline const Color operator*(const Color& p_Col1, const Color& p_Col2)
{
	return Color(p_Col1.m_R * p_Col2.m_R, p_Col1.m_G * p_Col2.m_G, p_Col1.m_B * p_Col2.m_B);
}

inline const Color operator*(const Color& p_Col, const float p_Factor)
{
	return Color(p_Col.m_R * p_Factor, p_Col.m_G * p_Factor, p_Col.m_B * p_Factor);
}

inline const Color operator*(const float p_Factor, const Color& p_Col)
{
	return Color(p_Col.m_R * p_Factor, p_Col.m_G * p_Factor, p_Col.m_B * p_Factor);
}

inline const Color operator/(const Color& p_Col1, const Color& p_Col2)
{
	return Color(p_Col1.m_R / p_Col2.m_R, p_Col1.m_G / p_Col2.m_G, p_Col1.m_B / p_Col2.m_B);
}

inline const Color operator/(const Color& p_Col, const float p_Factor)
{
	return Color(p_Col.m_R / p_Factor, p_Col.m_G / p_Factor, p_Col.m_B / p_Factor);
}

inline const Color operator/(const float p_Factor, const Color& p_Col)
{
	return Color(p_Col.m_R / p_Factor, p_Col.m_G / p_Factor, p_Col.m_B / p_Factor);
}

inline float Color::Sum() const
{
	return m_R + m_G + m_B;
}

inline float Color::Average() const
{
	return (m_R + m_G + m_B) / 3.0f;
}

inline bool Color::IsZero() const
{
	return (Sum() <= 0.0f);
}

inline Color Gamma(const Color& p_Col, float y)
{
	using namespace Math;
	return Color(Pow(p_Col.m_R, 1.0f / y), Pow(p_Col.m_G, 1.0f / y), Pow(p_Col.m_B, 1.0f / y));
}

inline float Gamma(float x, float y)
{
	using namespace Math;
	return Pow(x, 1.0f / y);
}

// converts color into an RGBE value
inline void Color::ToRGBE(byte p_RGBE[4]) const
{
	float v;
	int e;

	// v is the largest of the r g b
	v = m_R;
	if(m_G > v) v = m_G;
	if(m_B > v) v = m_B;

	if(v < 1e-32f)
		p_RGBE[0] = p_RGBE[1] = p_RGBE[2] = p_RGBE[3] = 0;
	else
	{
		float m = v;
		int n = 0;

		// get mantissa and exponent
		if(v > 1.0)
		{
			while(m > 1.0f)
			{
				m *= 0.5f;
				n++;
			}
		}
		else if(v <= 0.5f)
		{
			while(m <= 0.5f)
			{
				m *= 2.0f;
				n--;
			}
		}

		e = n;
		v =  m * 255.0f / v;

		p_RGBE[0] = static_cast<byte>(m_R * v);
		p_RGBE[1] = static_cast<byte>(m_G * v);
		p_RGBE[2] = static_cast<byte>(m_B * v);
		p_RGBE[3] = static_cast<byte>(e + 128);
	}
}

// converts an RGBE value into a color
inline void Color::FromRGBE(const byte p_RGBE[4])
{
	// non-zero pixel
	if(p_RGBE[3])
	{
		float f = 1.0f;
		int e = p_RGBE[3] - (128 + 8);

		// evaluate 2^e efficiently
		if(e > 0)
		{
			for(int i = 0; i < e; ++i)
				f *= 2.0f;
		}
		else
		{
			for(int i = 0; i < -e; ++i)
				f /= 2.0f;
		}

		m_R = (p_RGBE[0] + 0.5f) * f;
		m_G = (p_RGBE[1] + 0.5f) * f;
		m_B = (p_RGBE[2] + 0.5f) * f;
	}
	else
		m_R = m_G = m_B = 0.0f;
}

}

#endif // COLOR_H
